<html>
    <head>
        <title>加载关联实体</title>
    </head>
    <body>
        <script>
             /*
                    四、加载关联实体 (导航属性实体)

                                假设你有一个Order，其中包含OrderLines的集合，并且OrderLine具有Order的导航属性：

                                        using System;
                                        using System.Collections.Generic;
                                        using System.Collections.ObjectModel;
                                        using Volo.Abp.Auditing;
                                        using Volo.Abp.Domain.Entities;

                                        namespace MyCrm
                                        {
                                            public class Order : AggregateRoot<Guid>, IHasCreationTime
                                            {
                                                public Guid CustomerId { get; set; }
                                                public DateTime CreationTime { get; set; }

                                                public ICollection<OrderLine> Lines { get; set; } //集合导航属性

                                                public Order()
                                                {
                                                    Lines = new Collection<OrderLine>();
                                                }
                                            }

                                            public class OrderLine : Entity<Guid>
                                            {
                                                public Order Order { get; set; } // 导航属性
                                                public Guid OrderId { get; set; }

                                                public Guid ProductId { get; set; }
                                                public int Count { get; set; }
                                                public double UnitPrice { get; set; }
                                            }
                                        }

                                并定义了数据库映射如下所示：

                                        builder.Entity<Order>(b =>
                                        {
                                            b.ToTable("Orders");
                                            b.ConfigureByConvention();

                                            // 声明关系
                                            b.HasMany(x => x.Lines)
                                                .WithOne(x => x.Order)
                                                .HasForeignKey(x => x.OrderId)
                                                .IsRequired();
                                        });

                                        builder.Entity<OrderLine>(b =>
                                        {
                                            b.ToTable("OrderLines");
                                            b.ConfigureByConvention();
                                        });

                                当你查询Order时，你可能希望在单个查询中包含所有OrderLine，或者你可能希望稍后按需加载它们。


                                1、急切加载(Eager Loading)  / 加载详情 (Load With Details)

                                       当你想在查询实体时加载相关实体时，你有不同的选项:
                                        
                                            ①、Repository.WithDetails

                                                    IRepository.WithDetailsAsync(...)可用于通过包含一个关系集合/属性来获取IQueryable<T>

                                                    eg:

                                                            using System;
                                                            using System.Linq;
                                                            using System.Threading.Tasks;
                                                            using Volo.Abp.Domain.Repositories;
                                                            using Volo.Abp.Domain.Services;

                                                            namespace AbpDemo.Orders
                                                            {
                                                                public class OrderManager : DomainService
                                                                {
                                                                    private readonly IRepository<Order, Guid> _orderRepository;

                                                                    public OrderManager(IRepository<Order, Guid> orderRepository)
                                                                    {
                                                                        _orderRepository = orderRepository;
                                                                    }

                                                                    public async Task TestWithDetails(Guid id)
                                                                    {
                                                                        // 等于.net core 中的 .Include(x => x.Lines)，返回一个 IQueryable<T>
                                                                        var queryable = await _orderRepository.WithDetailsAsync(x => x.Lines);

                                                                        // 执行Linq过滤器
                                                                        var query = queryable.Where(x => x.Id == id);

                                                                        // 执行查询
                                                                        var order = await AsyncExecuter.FirstOrDefaultAsync(query);
                                                                    }
                                                                }
                                                            }

                                                    AsyncExecuter用于执行异步LINQ扩展，而不依赖于EF Core.

                                                    如果你将EF Core NuGet包引用添加到你的项目中，那么你可以直接使用await query.FirstOrDefaultAsync()。

                                                    但是，这次你依赖于域层中的EF Core。

                                                    eg: 获取订单列表及其行

                                                            public async Task TestWithDetails()
                                                            {
                                                                // Get a IQueryable<T> by including sub collections
                                                                var queryable = await _orderRepository.WithDetailsAsync(x => x.Lines);

                                                                // Execute the query and get the result
                                                                var orders = await AsyncExecuter.ToListAsync(queryable);
                                                            }

                                                    ★、DefaultWithDetailsFunc 

                                                            如果你没有将任何表达式传递给WithDetailsAsync方法，则它使用你提供的DefaultWithDetailsFunc选项包含所有详细信息。

                                                            你可以在EntityFrameworkCore项目中模块的ConfigureServices方法中为实体配置DefaultWithDetailsFunc。

                                                            eg：在查询Order时包含Lines

                                                                    Configure<AbpEntityOptions>(options =>
                                                                    {
                                                                        options.Entity<Order>(orderOptions =>
                                                                        {
                                                                            orderOptions.DefaultWithDetailsFunc = query => query.Include(o => o.Lines);
                                                                        });
                                                                    });

                                                            然后你可以在没有任何参数的情况下使用WithDetails：

                                                                     public async Task TestWithDetails()
                                                                    {
                                                                        // IQueryable<T> by including all sub collections
                                                                        var queryable = await _orderRepository.WithDetailsAsync();

                                                                        //Execute the query and get the result
                                                                        var orders = await AsyncExecuter.ToListAsync(queryable);
                                                                    }   

                                            ②、 存储库的Get/Find 方法
                                            
                                                        一些标准的Repository方法具有可选的includeDetails参数:

                                                                GetAsync和FindAsync获取includeDetails，默认值为true。

                                                                GetListAsync和GetPagedListAsync获取includeDetails，默认值为false。

                                                        这意味着，这些方法默认返回单个实体包含详细信息，而列表返回方法默认不包含详细信息.
                                                        
                                                        你可以显式传递includeDetails来更改行为。

                                                        ★、这些方法使用上面解释的DefaultWithDetailsFunc选项。

                                                        eg： 获取包含详细信息的订单

                                                                public async Task TestWithDetails(Guid id)
                                                                {
                                                                    var order = await _orderRepository.GetAsync(id);
                                                                }  

                                                        eg: 获取没有详细信息的订单

                                                                public async Task TestWithoutDetails(Guid id)
                                                                {
                                                                    var order = await _orderRepository.GetAsync(id, includeDetails: false);
                                                                }

                                                        eg: 获取包含详细信息的实体列表

                                                                public async Task TestWithDetails()
                                                                {
                                                                    var orders = await _orderRepository.GetListAsync(includeDetails: true);
                                                                }
                                            ③、替代品

                                                    存储库模式试图封装EF Core，因此你的选择有限。如果你需要高级场景，你可以遵循其中一个选项：

                                                                （1）创建自定义存储库方法并使用完整的EF Core API。

                                                                （2）引用项目中的Volo.Abp.EntityFrameworkCore包。这样，你可以在代码中直接使用Include和ThenInclude




                                2、显示加载/延迟加载   Explicit / Lazy Loading

                                            如果你在查询实体时不包含关系，并且以后需要访问导航属性或集合，你有不同的选择。
                                            

                                        ①、显式加载

                                                存储库提供 "EnsurePropertyLoadedAsync"和 "EnsureCollectionLoadedAsync"扩展方法来显式加载导航属性或子集合。

                                                eg: 需要时加载订单的行

                                                        public async Task TestWithDetails(Guid id)
                                                        {
                                                            var order = await _orderRepository.GetAsync(id, includeDetails: false);
                                                            // 此时order.Lines为null

                                                            await _orderRepository.EnsureCollectionLoadedAsync(order, x => x.Lines);
                                                            //order.Lines 已经被填充了值，不再是null了
                                                        } 
                                                            
                                                 EnsurePropertyLoadedAsync和EnsureCollectionLoadedAsync方法什么也不做。因此，多次调用没有问题。


                                        ②、延迟加载  Lazy Loading with Proxies

                                                在某些情况下可能无法显式加载，尤其是当你没有对Repository或DbContext的引用时。

                                                延迟加载是EF Core的一项功能，可在你第一次访问它时加载相关属性/集合。

                                                启用延迟加载:

                                                        (1)将Microsoft.EntityFrameworkCore.Proxies包安装到你的项目中（通常安装到EF Core集成项目中）

                                                        (2)为DbContext配置UseLazyLoadingProxies（在EF Core项目中模块的ConfigureServices方法中）。
                                                        
                                                                示例：
                                                                    Configure<AbpDbContextOptions>(options =>
                                                                    {
                                                                        options.PreConfigure<MyCrmDbContext>(opts =>
                                                                        {
                                                                            opts.DbContextOptions.UseLazyLoadingProxies(); // 启用懒加载
                                                                        });

                                                                        options.UseSqlServer();
                                                                    });

                                                        (3)、把你的导航属性设置为virtual

                                                                eg:
                                                                    public virtual ICollection<OrderLine> Lines { get; set; } //virtual 集合导航属性
                                                                    public virtual Order Order { get; set; } //virtual 导航属性

                                                启用延迟加载并获取实体后，你可以自由访问导航属性和集合：

                                                        public async Task TestWithDetails(Guid id)
                                                        {
                                                            var order = await _orderRepository.GetAsync(id);
                                                            //此时 order.Lines 是空的 

                                                            var lines = order.Lines;
                                                            // 现在  order.Lines 已经被填充了
                                                        }

                                                每当你访问属性/集合时，EF Core都会自动执行附加查询以从数据库中加载属性/集合。


                */
        </script>
    </body>
</html>